﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace ZeroBugBounce.Missive.Patterns
{
	public class AsyncFileReadHandler : TimeoutHandlerBase<AsyncFileReadRequest>
	{
		public AsyncFileReadHandler(TimeSpan timeout) : base(timeout) 
		{
			this.Cancelable = true;	
		}

		bool PastTimeout(DateTime startTime)
		{
			return (DateTime.UtcNow - startTime) > Timeout;
		}

		public override void Handle(HandlerContext<AsyncFileReadRequest> context)
		{
			context.Message.StartTime = DateTime.UtcNow;
			HandleWithTimeout(context);
		}

		protected override void HandleWithTimeout(HandlerContext<AsyncFileReadRequest> context)
		{
			var replyContext = context as ReplyHandlerContext<AsyncFileReadRequest, byte[]>;

			if (replyContext == null)
			{
				// can't reply, there's no point in reading anything
				return;
			}

			string path = context.Message.Path;
			var readStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true);
			byte[] buffer = new byte[4096];

			readStream.BeginRead(buffer, 0, 4096, ReadCallback, new AsyncFileReadState
			{
				Buffer = buffer,
				HandlerContext = replyContext,
				ReadStream = readStream
			});
		}

		void ReadCallback(IAsyncResult ar)
		{
			AsyncFileReadState readInfo = (AsyncFileReadState)ar.AsyncState;

			var buffer = readInfo.Buffer;
			var readStream = readInfo.ReadStream;
			var replyCallback = readInfo.HandlerContext.ReplyCallback;

			// if timeout or cancelled:
			if (PastTimeout(readInfo.HandlerContext.Message.StartTime)
				|| readInfo.HandlerContext.CancellationToken.IsCancellationRequested)
			{
				readStream.EndRead(ar);
				readStream.Close();
				RaiseTimeoutError(readInfo.HandlerContext.Message);
				return;
			}

			int bytesRead = readStream.EndRead(ar);

			if (bytesRead == 4096)
			{
				Array.Resize(ref buffer, buffer.Length + 4096);
				readInfo.Buffer = buffer;
				readStream.BeginRead(buffer, buffer.Length - 4096, 4096, ReadCallback, readInfo);
			}
			else
			{
				if (bytesRead < 4096)
				{
					Array.Resize(ref buffer, buffer.Length - (4096 - bytesRead));
				}

				// if timeout or cancelled:
				if (PastTimeout(readInfo.HandlerContext.Message.StartTime)
					|| readInfo.HandlerContext.CancellationToken.IsCancellationRequested)
				{
					readStream.Close();
					RaiseTimeoutError(readInfo.HandlerContext.Message);
					return;
				}

				replyCallback(buffer);
			}
		}
	}

	public class AsyncFileReadRequest
	{
		public AsyncFileReadRequest(string path)
		{
			Path = path;
		}

		public string Path { get; private set; }

		internal DateTime StartTime { get; set; }
	}

	public class AsyncFileReadState
	{
		public DateTime StartTime { get; set; }
		public byte[] Buffer { get; set; }
		public ReplyHandlerContext<AsyncFileReadRequest, byte[]> HandlerContext { get; set; }
		public FileStream ReadStream { get; set; }
	}
}
